//
// Created by jack on 2021/2/2.
//

#include "weilapb_defines.h"

#include <WL.Service.pb.h>
#include <pb.h>
#include <pb_decode.h>
#include <pb_encode.h>

#include "weilapb_datafuns.h"

#include "weilapb_error.h"
#include "weilapb_internal.h"

PB_BOOL weilapb_decodeCallback(pb_istream_t* stream, const pb_field_t* field, void** arg) {
    PB_UINT8     fieldRule   = PB_HTYPE(field->type);
    PB_UINT8     fieldType   = PB_LTYPE(field->type);
    PBFieldData* pbFiledData = (PBFieldData*)(*arg);

    if (fieldRule == PB_HTYPE_REPEATED) {
        if (fieldType == PB_LTYPE_STRING) {
            PBString*  item;
            PBString** pList;
            if (pbFiledData->dataSize == pbFiledData->maxSize) {
                PBString** newList = (PBString**)PB_ALLOC(2 * pbFiledData->maxSize * sizeof(PBString*));
                if (newList == PB_NULL) {
                    return PB_FALSE;
                }

                memcpy(newList, pbFiledData->pData, pbFiledData->dataSize * sizeof(PBString*));
                pbFiledData->maxSize *= 2;
                PB_FREE(pbFiledData->pData);
                pbFiledData->pData = newList;
            }

            item = PB_ALLOC(sizeof(PBString) + stream->bytes_left);
            if (item == PB_NULL) {
                return PB_FALSE;
            }

            item->strLen = stream->bytes_left;
            strcpy(item->str, (char*)stream->state);
            pList                        = (PBString**)pbFiledData->pData;
            pList[pbFiledData->dataSize] = item;
            pbFiledData->dataSize++;
        } else if (fieldType == PB_LTYPE_VARINT || fieldType == PB_LTYPE_UVARINT || fieldType == PB_LTYPE_FIXED32
                   || fieldType == PB_LTYPE_FIXED64) {
            while (stream->bytes_left) {
                if (pbFiledData->dataSize == pbFiledData->maxSize) {
                    void* pNewData = PB_NULL;
                    if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_INT32
                        || pbFiledData->dataType == PB_DATA_TYPE_ARRAY_UINT32) {
                        PB_UINT32* newList = (PB_UINT32*)PB_ALLOC(2 * pbFiledData->maxSize * sizeof(PB_UINT32));
                        if (newList == PB_NULL) {
                            return PB_FALSE;
                        }
                        memcpy(newList, pbFiledData->pData, pbFiledData->dataSize * sizeof(PB_UINT32));
                        pNewData = newList;
                    } else if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_INT64
                               || pbFiledData->dataType == PB_DATA_TYPE_ARRAY_UINT64) {
                        PB_UINT64* newList = (PB_UINT64*)PB_ALLOC(2 * pbFiledData->maxSize * sizeof(PB_UINT64));
                        if (newList == PB_NULL) {
                            return PB_FALSE;
                        }
                        memcpy(newList, pbFiledData->pData, pbFiledData->dataSize * sizeof(PB_UINT64));
                        pNewData = newList;
                    } else if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_DOUBLE) {
                        PB_DOUBLE* newList = (PB_DOUBLE*)PB_ALLOC(2 * pbFiledData->maxSize * sizeof(PB_DOUBLE));
                        if (newList == PB_NULL) {
                            return PB_FALSE;
                        }
                        memcpy(newList, pbFiledData->pData, pbFiledData->dataSize * sizeof(PB_DOUBLE));
                        pNewData = newList;
                    } else if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_FLOAT) {
                        PB_FLOAT* newList = (PB_FLOAT*)PB_ALLOC(2 * pbFiledData->maxSize * sizeof(PB_FLOAT));
                        if (newList == PB_NULL) {
                            return PB_FALSE;
                        }
                        memcpy(newList, pbFiledData->pData, pbFiledData->dataSize * sizeof(PB_FLOAT));
                        pNewData = newList;
                    }

                    pbFiledData->maxSize *= 2;
                    PB_FREE(pbFiledData->pData);
                    pbFiledData->pData = pNewData;
                }

                if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_INT32
                    || pbFiledData->dataType == PB_DATA_TYPE_ARRAY_UINT32) {
                    PB_UINT32 value = 0;
                    if (pb_decode_varint32(stream, &value)) {
                        ((PB_UINT32*)pbFiledData->pData)[pbFiledData->dataSize] = value;
                        pbFiledData->dataSize++;
                    } else {
                        return PB_FALSE;
                    }
                } else if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_INT64
                           || pbFiledData->dataType == PB_DATA_TYPE_ARRAY_UINT64) {
                    PB_UINT64 value = 0;
                    if (pb_decode_varint(stream, &value)) {
                        ((PB_UINT64*)pbFiledData->pData)[pbFiledData->dataSize] = value;
                        pbFiledData->dataSize++;
                    } else {
                        return PB_FALSE;
                    }
                } else if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_DOUBLE) {
                    PB_DOUBLE value = 0.0;
                    if (pb_decode_fixed64(stream, &value)) {
                        ((PB_DOUBLE*)pbFiledData->pData)[pbFiledData->dataSize] = value;
                        pbFiledData->dataSize++;
                    } else {
                        return PB_FALSE;
                    }
                } else if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_FLOAT) {
                    PB_FLOAT value = 0.0;
                    if (pb_decode_fixed32(stream, &value)) {
                        ((PB_FLOAT*)pbFiledData->pData)[pbFiledData->dataSize] = value;
                        pbFiledData->dataSize++;
                    } else {
                        return PB_FALSE;
                    }
                }
            }
        } else if (fieldType == PB_LTYPE_SUBMESSAGE) {
            if (pbFiledData->decSubMsg) {
                if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_SUBMSG) {
                    void* pObj;
                    if (pbFiledData->dataSize == pbFiledData->maxSize) {
                        void** newList = (void**)PB_ALLOC(2 * pbFiledData->maxSize * sizeof(void*));
                        if (newList == PB_NULL) {
                            return PB_FALSE;
                        }

                        memcpy(newList, pbFiledData->pData, pbFiledData->dataSize * sizeof(void*));
                        pbFiledData->maxSize *= 2;
                        PB_FREE(pbFiledData->pData);
                        pbFiledData->pData = newList;
                    }

                    pObj = pbFiledData->decSubMsg(stream, field);
                    if (pObj) {
                        ((void**)pbFiledData->pData)[pbFiledData->dataSize] = pObj;
                        pbFiledData->dataSize++;
                    } else {
                        return PB_FALSE;
                    }
                } else {
                    PBLinkItem* pLinkItem = pbFiledData->decSubMsg(stream, field);
                    if (pLinkItem) {
                        PBLinkList* pList = (PBLinkList*)pbFiledData->pData;
                        PBLinkItem* pRoot = pList->pLinks;
                        if (pList->listCount == 0) {
                            pList->pLinks = pLinkItem;
                        } else {
                            while (pRoot->pNext)
                                pRoot = pRoot->pNext;
                            pRoot->pNext = pLinkItem;
                        }

                        pList->listCount++;

                        return PB_TRUE;
                    } else {
                        weilapb_freePBLinkList((PBLinkList*)pbFiledData->pData);
                    }
                    return PB_FALSE;
                }
            } else {
                return PB_FALSE;
            }
        }
    } else if (fieldRule == PB_HTYPE_OPTIONAL || fieldRule == PB_HTYPE_REQUIRED) {
        if (fieldType == PB_LTYPE_STRING || fieldType == PB_LTYPE_BYTES) {
            void* pNewData = PB_NULL;

            if (pbFiledData->dataType == PB_DATA_TYPE_DYNAMIC_STRING) {
                if (pbFiledData->maxSize < stream->bytes_left) {
                    pNewData = (void*)PB_ALLOC(stream->bytes_left + 1);
                    if (pNewData == PB_NULL) {
                        return PB_FALSE;
                    }

                    PB_FREE(pbFiledData->pData);
                    pbFiledData->pData   = pNewData;
                    pbFiledData->maxSize = stream->bytes_left + 1;
                }

                memcpy(pbFiledData->pData, stream->state, stream->bytes_left);
                pbFiledData->dataSize = stream->bytes_left;
            } else {
                if (pbFiledData->maxSize < stream->bytes_left) {
                    if (fieldType == PB_LTYPE_STRING) {
                        memcpy(pbFiledData->pData, stream->state, pbFiledData->maxSize - 1);
                    } else {
                        memcpy(pbFiledData->pData, stream->state, pbFiledData->maxSize);
                    }
                } else {
                    if (fieldType == PB_LTYPE_STRING) {
                        if (pbFiledData->maxSize == stream->bytes_left) {
                            memcpy(pbFiledData->pData, stream->state, stream->bytes_left - 1);
                        } else {
                            memcpy(pbFiledData->pData, stream->state, stream->bytes_left);
                        }
                    } else {
                        memcpy(pbFiledData->pData, stream->state, stream->bytes_left);
                    }
                }
            }
        }
    }

    return PB_TRUE;
}

PB_BOOL weilapb_encodeCallback(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) {
    PB_UINT8           fieldRule   = PB_HTYPE(field->type);
    PB_UINT8           fieldType   = PB_LTYPE(field->type);
    const PBFieldData* pbFiledData = (const PBFieldData*)(*arg);

    if (fieldRule == PB_HTYPE_REPEATED) {
        PB_UINT16 count = 0;
        while (count < pbFiledData->dataSize) {
            pb_encode_tag_for_field(stream, field);
            if (fieldType == PB_LTYPE_VARINT || fieldType == PB_LTYPE_UVARINT) {
                if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_UINT32
                    || pbFiledData->dataType == PB_DATA_TYPE_ARRAY_INT32) {
                    if (!pb_encode_varint(stream, (PB_UINT64)((PB_UINT32*)pbFiledData->pData)[count])) {
                        return PB_FALSE;
                    }
                    count++;
                } else if (pbFiledData->dataType == PB_DATA_TYPE_ARRAY_UINT64
                           || pbFiledData->dataType == PB_DATA_TYPE_ARRAY_INT64) {
                    if (!pb_encode_varint(stream, ((PB_UINT64*)pbFiledData->pData)[count])) {
                        return PB_FALSE;
                    }
                    count++;
                }
            } else if (fieldType == PB_LTYPE_FIXED32) {
                if (!pb_encode_fixed32(stream, &(((PB_FLOAT*)pbFiledData->pData)[count]))) {
                    return PB_FALSE;
                }
                count++;
            } else if (fieldType == PB_LTYPE_FIXED64) {
                if (!pb_encode_fixed64(stream, &(((PB_DOUBLE*)pbFiledData->pData)[count]))) {
                    return PB_FALSE;
                }
                count++;
            } else if (fieldType == PB_LTYPE_SUBMESSAGE) {
                if (!pbFiledData->encSubMsg(
                        stream,
                        field,
                        (const void*)((PB_UINT8*)pbFiledData->pData + pbFiledData->subMsgSize * count))) {
                    return PB_FALSE;
                }

                count++;
            } else if (fieldType == PB_LTYPE_STRING) {
                PBString* pbString = ((PBString**)(pbFiledData->pData))[count];
                if (pb_encode_tag_for_field(stream, field)) {
                    if (pb_encode_string(stream, (const pb_byte_t*)pbString->str, pbString->strLen)) {
                        return PB_FALSE;
                    }
                } else {
                    return PB_FALSE;
                }

                count++;
            }
        }
    } else if (fieldRule == PB_HTYPE_REQUIRED || fieldRule == PB_HTYPE_OPTIONAL) {
        if (pb_encode_tag_for_field(stream, field)) {
            // string, bytes
            if (!pb_encode_string(stream, (const pb_byte_t*)pbFiledData->pData, pbFiledData->dataSize)) {
                return PB_FALSE;
            }
        } else {
            return PB_FALSE;
        }
    }

    return PB_TRUE;
}

void weilapb_initDecDataItem(PBFieldData* pFiledData, PB_INT16 dataType, PB_INT32 maxDataSize, void* pData) {
    pFiledData->dataType = dataType;
    pFiledData->maxSize  = maxDataSize;
    pFiledData->dataSize = 0;
    pFiledData->pData    = pData;
}

/**
 * 对于编码来说，如果是
 * @param pFiledData
 * @param dataType 指定是何种类型的数据，如果类型是子消息的话，则必须手动设置PBFieldData的两个属性subMsgSize和encSubMsg
 * @param dataSize 如果类型涉及的是数组类型，则size代表的是数组的个数
 * @param pData
 */
void weilapb_initEncDataItem(PBFieldData* pFiledData, PB_INT16 dataType, PB_INT32 dataSize, void* pData) {
    memset(pFiledData, 0, sizeof(PBFieldData));
    pFiledData->dataType = dataType;
    pFiledData->maxSize  = dataSize;
    pFiledData->dataSize = dataSize;
    pFiledData->pData    = pData;
}

void weilapb_setDecCallback(PBFieldData* pFieldData, pb_callback_t* pCallback, decodeCallback callback) {
    if (callback) {
        pCallback->funcs.decode = callback;
    } else {
        pCallback->funcs.decode = weilapb_decodeCallback;
    }

    pCallback->arg = pFieldData;
}

void weilapb_setEncCallback(PBFieldData* pFieldData, pb_callback_t* pCallback, encodeCallback callback) {
    if (callback) {
        pCallback->funcs.encode = callback;
    } else {
        pCallback->funcs.encode = weilapb_encodeCallback;
    }

    pCallback->arg = pFieldData;
}

PB_BOOL weilapb_decodeWeilaMsg(PBWeilaMsg* pWeilaMsg, pb_istream_t* pbIstream, void* serviceMsgData) {
    if (pb_decode(pbIstream, WL_Service_ServiceMessage_fields, serviceMsgData)) {
        pWeilaMsg->commandId   = ((WL_Service_ServiceMessage*)serviceMsgData)->serviceHead.commandId;
        pWeilaMsg->serviceId   = ((WL_Service_ServiceMessage*)serviceMsgData)->serviceHead.serviceId;
        pWeilaMsg->commandType = ((WL_Service_ServiceMessage*)serviceMsgData)->serviceHead.commandType;
        pWeilaMsg->resultCode  = ((WL_Service_ServiceMessage*)serviceMsgData)->serviceHead.resultCode;
        pWeilaMsg->seqNum      = ((WL_Service_ServiceMessage*)serviceMsgData)->serviceHead.seq;
        return PB_TRUE;
    }

    weilapb_error(ERR_DECODE, "decode msg failed:%s\n", pbIstream->errmsg ? pbIstream->errmsg : "unknown");
    PB_FREE(pWeilaMsg);
    return PB_FALSE;
}

PB_BOOL weilapb_encodeWeilaMsg(void* serviceMsgData, PBEncResult* pEncResult) {
    pb_ostream_t   ostream;
    WeilaMsgHeader weilaMsgHeader;
    PB_UINT32      encodedServiceMsgSize = 0;

    memset(&ostream, 0, sizeof(pb_ostream_t));
    if (pb_encode(&ostream, WL_Service_ServiceMessage_fields, serviceMsgData)) {
        PB_UINT8*                  pBuffer;
        WL_Service_ServiceMessage* pServiceMsg;
        encodedServiceMsgSize = ostream.bytes_written;
        pBuffer               = (PB_UINT8*)PB_ALLOC(WEILA_MESSAGE_HEADER_LENGTH + encodedServiceMsgSize);
        if (pBuffer == PB_NULL) {
            weilapb_error(ERR_OUT_OF_MEMORY, "allocat mem for ostream fail");
            pEncResult->errCode = ERR_OUT_OF_MEMORY;
            pEncResult->pErrMsg = "allocat mem for ostream fail";
            return PB_FALSE;
        }

        memset(&ostream, 0, sizeof(pb_ostream_t));
        ostream = pb_ostream_from_buffer((PB_UINT8*)(pBuffer + WEILA_MESSAGE_HEADER_LENGTH), encodedServiceMsgSize);

        if (!pb_encode(&ostream, WL_Service_ServiceMessage_fields, serviceMsgData)) {
            PB_FREE(pBuffer);
            weilapb_error(ERR_ENCODE, "encode fail:%s\n", ostream.errmsg ? ostream.errmsg : "unknown");
            pEncResult->errCode = ERR_ENCODE;
            pEncResult->pErrMsg = ostream.errmsg;
            return PB_FALSE;
        }

        pServiceMsg              = (WL_Service_ServiceMessage*)serviceMsgData;
        weilaMsgHeader.serviceId = pServiceMsg->serviceHead.serviceId;
        weilaMsgHeader.commandId = pServiceMsg->serviceHead.commandId;
        weilaMsgHeader.seq       = pServiceMsg->serviceHead.seq;
        weilaMsgHeader.length    = WEILA_MESSAGE_HEADER_LENGTH + encodedServiceMsgSize;
        weilaMsgHeader.version   = 0;
        weilaMsgHeader.flag      = 0;
        weilaMsgHeader.reserved  = 0;

        weilapb_encodeWeilaMsgHeader(&weilaMsgHeader, pBuffer);

        pEncResult->seq             = weilaMsgHeader.seq;
        pEncResult->errCode         = ERR_NONE;
        pEncResult->commandId       = weilaMsgHeader.commandId;
        pEncResult->serviceId       = weilaMsgHeader.serviceId;
        pEncResult->encodedDataSize = encodedServiceMsgSize + WEILA_MESSAGE_HEADER_LENGTH;
        pEncResult->pEncodedData    = pBuffer;

        return PB_TRUE;
    } else {
        weilapb_error(ERR_ENCODE, "encode fail:%s\n", ostream.errmsg ? ostream.errmsg : "unknown");
        pEncResult->errCode = ERR_ENCODE;
        pEncResult->pErrMsg = ostream.errmsg;
    }

    return PB_FALSE;
}

void* weilapb_buildServiceMsg(PB_UINT32 serviceId, PB_UINT32 commandId, PB_UINT32 commandType) {
    WL_Service_ServiceMessage* pServiceMessage =
        (WL_Service_ServiceMessage*)PB_ALLOC(sizeof(WL_Service_ServiceMessage));

    if (pServiceMessage == NULL) {
        weilapb_error(ERR_OUT_OF_MEMORY, "allocate ServiceMessage fail");
        return NULL;
    }

    pServiceMessage->has_serviceHead             = true;
    pServiceMessage->serviceHead.has_serviceId   = true;
    pServiceMessage->serviceHead.serviceId       = serviceId;
    pServiceMessage->serviceHead.has_commandId   = true;
    pServiceMessage->serviceHead.commandId       = commandId;
    pServiceMessage->serviceHead.has_commandType = true;
    pServiceMessage->serviceHead.commandType     = commandType;
    pServiceMessage->serviceHead.has_seq         = true;
    pServiceMessage->serviceHead.seq             = ++pWeilaCtx->seq;

    return pServiceMessage;
}

PBWeilaMsg* weilapb_decodeEmptyRsp(const PB_UINT8* pData, PB_UINT32 dataSize) {
    WL_Service_ServiceMessage serviceMessage = WL_Service_ServiceMessage_init_zero;
    pb_istream_t              istream;
    PBWeilaMsg*               pWeilaMsg = PB_ALLOC(sizeof(PBWeilaMsg));

    istream = pb_istream_from_buffer(pData, dataSize);

    if (pWeilaMsg) {
        pWeilaMsg->contentSize = 0;
        if (weilapb_decodeWeilaMsg(pWeilaMsg, &istream, &serviceMessage)) {
            return pWeilaMsg;
        }

        weilapb_error(ERR_DECODE, "decode msg data fail");
        PB_FREE(pWeilaMsg);
        pWeilaMsg = PB_NULL;
    } else {
        weilapb_error(ERR_OUT_OF_MEMORY, "allocate weila msg failed");
    }

    return pWeilaMsg;
}

void weilapb_freePBLinkList(PBLinkList* pLinkList) {
    PBLinkItem* pItem = pLinkList->pLinks;
    if (pLinkList->listCount) {
        while (pItem) {
            PBLinkItem* pTemp = pItem;
            pItem             = pItem->pNext;
            PB_FREE(pTemp);
        }
    }
}

void weilapb_addRecyclePointer(PBWeilaMsg* pPbWeilaMsg, PB_UINT32 ptrType, void* ptr) {
    pPbWeilaMsg->recyclePtrData[pPbWeilaMsg->recyclePtrCount].pointerType = ptrType;
    pPbWeilaMsg->recyclePtrData[pPbWeilaMsg->recyclePtrCount].pPtr        = ptr;
    pPbWeilaMsg->recyclePtrCount++;
}

void weilapb_freeMsgDataLink(PBLinkList* pList) {
    PBLinkItem* pPbLinkItem = pList->pLinks;
    while (pPbLinkItem) {
        PBLinkItem* pTemp;
        PBMsgData*  pPbMsgData = (PBMsgData*)(pPbLinkItem->data);
        if (pPbMsgData->msgType == IM_PTT_DATA_TYPE || pPbMsgData->msgType == IM_VOIP_DATA_TYPE) {
            PBPtt* pPtt = (PBPtt*)pPbMsgData->content;
            weilapb_freePBLinkList(&pPtt->frameList);
        } else if (pPbMsgData->msgType == IM_AUDIO_DATA_TYPE) {
            weilapb_freePBLinkList((PBLinkList*)pPbMsgData->content);
        }

        pTemp       = pPbLinkItem;
        pPbLinkItem = pPbLinkItem->pNext;
        PB_FREE(pTemp);
    }
}
